﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class TextRevealer : MonoBehaviour
{
    [Header("效果配置")] public int numCharactersFade = 3;
    public float charsPerSecond = 30;
    public float smoothSeconds = 0.75f;
    [Header("组件引用")] public Text text;
    [Header("Event")] public UnityEvent allRevealed = new UnityEvent();

    private string originalString;
    private int revealedCharacters;
    private bool isRevealing = false;

    public bool IsRevealing => isRevealing;

    void Start()
    {
        if (string.IsNullOrEmpty(originalString))
        {
            RestartWithText(text.text);
        }
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0) && !isRevealing)
        {
            RevealNextParagraphAsync();
        }
    }

    public void RestartWithText(string strText)
    {
        revealedCharacters = 0;
        originalString = strText;
        text.text = BuildPartiallyRevealedString(originalString, keyCharIndex: -1, minIndex: 0, maxIndex: 0,
            fadeLength: numCharactersFade);
    }

    public void ShowEverythingWithoutAnimation()
    {
        StopAllCoroutines();

        text.text = originalString;
        revealedCharacters = originalString.Length;
        isRevealing = false;

        allRevealed.Invoke();
    }

    public void ShowNextParagraphWithoutAnimation()
    {
        if (IsAllRevealed()) return;

        StopAllCoroutines();

        var paragraphEnd = GetNextParagraphEnd(revealedCharacters);
        text.text = BuildPartiallyRevealedString(original: originalString,
            keyCharIndex: paragraphEnd,
            minIndex: revealedCharacters,
            maxIndex: paragraphEnd,
            fadeLength: 0);

        revealedCharacters = paragraphEnd + 1;
        while (revealedCharacters < originalString.Length && originalString[revealedCharacters] == '\n')
            revealedCharacters += 1;

        if (IsAllRevealed())
            allRevealed.Invoke();

        isRevealing = false;
    }

    public void RevealNextParagraphAsync()
    {
        StartCoroutine(RevealNextParagraph());
    }

    public IEnumerator RevealNextParagraph()
    {
        if (IsAllRevealed() || isRevealing) yield break;

        var paragraphEnd = GetNextParagraphEnd(revealedCharacters);
        if (paragraphEnd < 0) yield break;

        isRevealing = true;

        var keyChar = (float) (revealedCharacters - numCharactersFade);
        var keyCharEnd = paragraphEnd;
        var speed = 0f;
        var secondsElapsed = 0f;

        while (keyChar < keyCharEnd)
        {
            secondsElapsed += Time.deltaTime;
            if (secondsElapsed <= smoothSeconds)
                speed = Mathf.Lerp(0f, charsPerSecond, secondsElapsed / smoothSeconds);
            else
            {
                var secondsLeft = (keyCharEnd - keyChar) / charsPerSecond;
                if (secondsLeft < smoothSeconds)
                    speed = Mathf.Lerp(charsPerSecond, 0.1f * charsPerSecond, 1f - secondsLeft / smoothSeconds);
            }

            keyChar = Mathf.MoveTowards(keyChar, keyCharEnd, speed * Time.deltaTime);
            text.text = BuildPartiallyRevealedString(original: originalString,
                keyCharIndex: keyChar,
                minIndex: revealedCharacters,
                maxIndex: paragraphEnd,
                fadeLength: numCharactersFade);

            yield return null;
        }

        revealedCharacters = paragraphEnd + 1;

        while (revealedCharacters < originalString.Length && originalString[revealedCharacters] == '\n')
            revealedCharacters += 1;

        if (IsAllRevealed())
            allRevealed.Invoke();

        isRevealing = false;
    }

    public bool IsAllRevealed()
    {
        return revealedCharacters >= originalString.Length;
    }

    private int GetNextParagraphEnd(int startingFrom)
    {
        var paragraphEnd = originalString.IndexOf('\n', startingFrom);
        if (paragraphEnd < 0 && startingFrom < originalString.Length) paragraphEnd = originalString.Length - 1;
        return paragraphEnd;
    }

    private string BuildPartiallyRevealedString(string original, float keyCharIndex, int minIndex, int maxIndex,
        int fadeLength)
    {
        var lastFullyVisibleChar = Mathf.Max(Mathf.CeilToInt(keyCharIndex), minIndex - 1);
        var firstFullyInvisibleChar = (int) Mathf.Min(keyCharIndex + fadeLength, maxIndex) + 1;

        var revealed = original.Substring(0, lastFullyVisibleChar + 1);
        var unrevealed = original.Substring(lastFullyVisibleChar + 1);

        var sb = new StringBuilder();
        sb.Append(revealed);
        if (keyCharIndex > -1)
        {
            for (var i = lastFullyVisibleChar + 1; i < firstFullyInvisibleChar; i++)
            {
                var c = original[i];
                var originalColorRGB = ColorUtility.ToHtmlStringRGB(text.color);
                var alpha = Mathf.RoundToInt(255 * (keyCharIndex - i) / (float) fadeLength);
                sb.AppendFormat("<color=#{0}{1:X2}>{2}</color>", originalColorRGB, (byte) alpha, c);
            }

            sb.AppendFormat("<color=#00000000>{0}</color>", unrevealed);
        }

        return sb.ToString();
    }
}